#ifndef _MATHEMATICS_ALGEBRA_EXPRESSION_
#define _MATHEMATICS_ALGEBRA_EXPRESSION_

#include "term.h"
#include "set.h"
#include "..\..\utilities\string_helper.h"
using namespace science::utilities;

#include <iostream>

namespace science
{
	namespace mathematics
	{
		namespace algebra
		{
			class expression
			{
			private:
				mutable set<term> _terms;

			public:
				expression()
				{
					_terms.remove_all();
				}

				expression(std::string expression_str)
				{
					unsigned int index = 0;
					*this = parse_expression(expression_str, index);
				}

				~expression()
				{
					_terms.remove_all();
				}

			public:

				bool operator==(const expression& exp)
				{
					return (const_cast<const expression*>(this))->operator==(exp);
				}

				bool operator!=(const expression& exp)
				{
					return (const_cast<const expression*>(this))->operator!=(exp);
				}

				bool operator==(const expression& exp) const
				{
					expression temp = expression::simplify(*this);
					expression exp_temp = expression::simplify(exp);
					
					unsigned int terms_count = temp.get_terms_count();
					unsigned int exp_terms_count = exp_temp.get_terms_count();

					if (terms_count != exp_terms_count)
						return false;

					unsigned int index = 0;
					while (index < terms_count)
					{
						term t1 = temp.get_term(index);
						unsigned int index2 = 0;
						bool equals = false;
						while (index2 < exp_terms_count)
						{
							term t2 = exp_temp.get_term(index2);
							if (t2 == t1)
							{
								equals = true;
								break;
							}
							index2++;
						}	
						if (equals == false)
							return false;
						index++;
					}

					return true;
				}

				bool operator!=(const expression& exp) const
				{
					return !(this->operator==(exp));
				}

				expression& operator+=(const term& rhs)
				{
					*this = this->operator+(rhs);
					return *this;
				}

				expression operator+(const term& rhs)
				{
					return (const_cast<const expression*>(this))->operator+(rhs);
				}

				expression operator+(const term& rhs) const
				{
					expression exp = *this;
					exp._terms.add_element(rhs);
					exp = exp.simplify();
					correct_expression();
					return exp;
				}

				expression& operator+=(const expression& rhs)
				{
					*this = this->operator+(rhs);
					return *this;
				}

				expression operator+(const expression& rhs)
				{
					return (const_cast<const expression*>(this))->operator+(rhs);
				}

				expression operator+(const expression& rhs) const
				{
					expression result = *this;
					for (const auto& t : rhs)
						result += t;
					result = result.simplify();
					return result;
				}

				expression& operator-=(const term& rhs)
				{
					*this = this->operator-(rhs);
					return *this;
				}

				expression operator-(const term& rhs)
				{
					return (const_cast<const expression*>(this))->operator-(rhs);
				}

				expression operator-(const term& rhs) const
				{
					term temp_term = rhs;
					temp_term.set_coeffiecient(-rhs.get_coeffiecient());
					return this->operator+(temp_term);
				}

				expression& operator-=(const expression& rhs)
				{
					*this = this->operator-(rhs);
					return *this;
				}

				expression operator-(const expression& rhs)
				{
					return (const_cast<const expression*>(this))->operator-(rhs);
				}

				expression operator-(const expression& rhs) const
				{
					expression result = *this;
					for (const auto& t : rhs)
						result -= t;
					result = result.simplify();
					return result;
				}

				expression& operator*=(const term& rhs)
				{
					*this = this->operator*(rhs);
					return *this;
				}

				expression operator*(const term& rhs)
				{
					return (const_cast<const expression*>(this))->operator*(rhs);
				}

				expression operator*(const term& rhs) const
				{
					expression exp;
					
					for (auto t : _terms)
						exp += t * rhs;

					exp = exp.simplify();
					return exp;
				}

				expression& operator*=(const expression& rhs)
				{
					*this = this->operator*(rhs);
					return *this;
				}

				expression operator*(const expression& rhs)
				{
					return (const_cast<const expression*>(this))->operator*(rhs);
				}

				expression operator*(const expression& rhs) const
				{
					expression exp;

					for (auto t : _terms)
						exp += rhs * t;

					exp = exp.simplify();
					return exp;
				}

				expression& operator/=(const term& rhs)
				{
					*this = this->operator*(rhs);
					return *this;
				}

				expression operator/(const term& rhs)
				{
					return (const_cast<const expression*>(this))->operator*(rhs);
				}

				expression operator/(const term& rhs) const
				{
					expression exp;

					for (auto t : _terms)
						exp += t / rhs;

					exp = exp.simplify();
					return exp;
				}

				expression& operator*=(double rhs)
				{
					*this = this->operator*(rhs);
					return *this;
				}

				expression operator*(double rhs)
				{
					return (const_cast<const expression*>(this))->operator*(rhs);
				}

				expression operator*(double rhs) const
				{
					expression exp;

					for (auto t : _terms)
						exp += t * rhs;

					exp = exp.simplify();
					return exp;
				}

				expression& operator/=(double rhs)
				{
					*this = this->operator/(rhs);
					return *this;
				}

				expression operator/(double rhs)
				{
					return (const_cast<const expression*>(this))->operator/(rhs);
				}

				expression operator/(double rhs) const
				{
					if (math_helper::double_check(rhs, 0) == 0)
						throw new std::invalid_argument("Cannot divide by zero");

					expression exp;

					for (auto t : _terms)
						exp += t / rhs;

					exp = exp.simplify();
					return exp;
				}

			public:
				double evaluate(std::map<char, double> var_values)
				{
					double result = 0.0;
					for (auto& t : _terms)
						result += t.evaluate(var_values);
					return result;
				}

				double evaluate(char var_name, double var_value)
				{
					double result = 0.0;
					for (auto& t : _terms)
						result += t.evaluate(var_name, var_value);
					return result;
				}

				expression simplify()
				{
					return expression::simplify(*this);
				}

			public:
				
				unsigned int get_terms_count() const
				{
					return _terms.size();
				}

				term get_term(unsigned int index) const
				{
					if (index >= _terms.size())
						throw new std::invalid_argument("Invalid term index");

					return _terms.get_elemenet(index);
				}

				void remove_term(const term& t)
				{
					_terms.remove_element(t);
					correct_expression();
				}

				void remove_at(unsigned int index)
				{
					if (index >= _terms.size())
						throw new std::invalid_argument("Invalid term index");

					_terms.remove_at(index);
					correct_expression();
				}

				term get_max_power_term() const
				{
					term max_power;
					for (auto& t : _terms)
					{
						if (t.get_variables_count() > max_power.get_variables_count())
							max_power = t;
					}
					return max_power;
				}

			private:
				void correct_expression() const
				{
					term max_power = get_max_power_term();
					for (auto& t : _terms)
						t.resize_powers(max_power);
				}

			public:
				static expression simplify(const expression& exp)
				{
					expression temp = exp;
					expression exo_res;
					unsigned int terms_count = temp.get_terms_count();
					unsigned int index = 0;
					while (index < terms_count)
					{
						term t1 = temp.get_term(index);
						unsigned int index2 = index + 1;
						while (index2 < terms_count)
						{
							term t2 = temp.get_term(index2);
							if (t2.like(t1))
							{
								t1.set_coeffiecient(t1.get_coeffiecient() + t2.get_coeffiecient());
								temp.remove_at(index2);
								terms_count--;
							}
							index2++;
						}
						exo_res._terms.add_element(t1);
						index++;
					}

					expression temp_res;
					for (unsigned int i = 0; i < exo_res.get_terms_count(); i++)
						if (math_helper::double_check(exo_res.get_term(i).get_coeffiecient(), 0) != 0)
							temp_res._terms.add_element(exo_res.get_term(i));

					temp_res.correct_expression();
					return temp_res;
				}

				static expression parse_expression(std::string str, unsigned int& index)
				{
					str = string_helper::remove_white_chars(str);
					expression exp;

					if (index >= str.size())
						return exp;

					bool open_bracket = false;
					if (str[index] == '(')
					{
						open_bracket = true;
						index++;
					}
					term t = term::parse_term(str, index);
					exp += t;
					if (str[index] == '=' || str[index] == '>' || str[index] == '<')
						return exp;

					if (open_bracket)
					{
						if (str[index] == ')')
						{
							index++;
							return exp;
						}
					}

					while (index < str.size())
					{
						t = term::parse_term(str, index);
						exp += t;
						
						if (str[index] == '=' || str[index] == '>' || str[index] == '<')
							return exp;

						if (open_bracket)
						{
							if (str[index] == ')')
							{
								index++;
								return exp;
							}
						}
					}

					return exp;
				}
			public:
				std::vector<term>::iterator begin() { return _terms.begin(); }
				std::vector<term>::iterator end() { return _terms.end(); }
				std::vector<term>::const_iterator begin() const { return _terms.begin(); }
				std::vector<term>::const_iterator end() const { return _terms.end(); }

			public:
				friend std::ostream &operator<<(std::ostream &output, const expression &exp)
				{
					if (exp == expression::zero())
						output << "0";

					bool first = true;
					for (auto t : exp)
					{
						t.set_lead_term(first);
						output << t;
						first = false;
					}
					return output;
				}

				friend std::istream &operator>>(std::istream  &input, expression &exp)
				{
					unsigned int terms_count = 0;
					std::cout << "Enter terms count : ";
					input >> terms_count;
					if (terms_count > 0)
					{
						for (unsigned int i = 0; i < terms_count; i++)
						{
							std::cout << "Enter term (" << i << ")" << std::endl;
							term t;
							input >> t;
							exp._terms.add_element(t);
						}
					}
					return input;
				}

			public:
				static expression zero(std::map<char, double> variable_powers = std::map<char, double>())
				{
					return constant(0, variable_powers);
				}

				static expression one(std::map<char, double> variable_powers = std::map<char, double>())
				{
					return constant(1, variable_powers);
				}

				static expression constant(double cst, std::map<char, double> variable_powers = std::map<char, double>())
				{
					expression exp;
					exp += term(cst, variable_powers);
					return exp;
				}

				static expression unit(std::map<char, double> variable_powers = { std::pair<char, double>('x', 1) })
				{
					expression exp;
					exp += term(1, variable_powers);
					return exp;
				}
			};

			static expression operator+(const term& lhs, const expression& rhs)
			{
				return rhs.operator+(lhs);
			}

			static expression operator-(const term& lhs, const expression& rhs)
			{
				return rhs.operator-(lhs);
			}

			static expression operator*(const term& lhs, const expression& rhs)
			{
				return rhs.operator*(lhs);
			}

			static expression operator/(const term& lhs, const expression& rhs)
			{
				return rhs.operator/(lhs);
			}

			static expression operator*(double num, const expression& rhs)
			{
				return rhs.operator*(num);
			}

			static expression operator/(double num, const expression& rhs)
			{
				return rhs.operator/(num);
			}
		}
	}
}

#endif